package com.itcam.tingshu.common.login;

import com.itcam.tingshu.common.constant.RedisConstant;
import com.itcam.tingshu.common.execption.ItcamException;
import com.itcam.tingshu.common.result.ResultCodeEnum;
import com.itcam.tingshu.common.util.AuthContextHolder;
import com.itcam.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author : Cammy.Wu
 * Description : 登录切面类
 */

@Component
@Aspect
public class TsLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    //  @annotation(org.springframework.transaction.annotation.Transactional)
    //  利用spring.io 官方文档有提示!

    // 前置通知——请求进入之前
    //  @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")

    // 后置通知——处理完成之后，最后渲染之前
    // @After

    @Around("execution(* com.itcam.tingshu.*.api.*.*(..))&&@annotation(tsLogin)")
    public Object tsLogin(ProceedingJoinPoint joinPoint, TsLogin tsLogin) throws Throwable {
         /*
            登录成功之后，login() success：在请求头中存储一个token;如果没有登录过，请求头中没有token
                还需要将用户信息存储到缓存！缓存redis，使用数据类型是什么？key，value
                login;
                    hash:   存储对象的时候，使用hash数据结构  hset user:userId:info phone 13927328473；便于修改
                        hset key field value;
                        hget key field;
                        便于修改的原因，例如user:userId:info phone 13927328473；修改不需要动其他字段
                    string:     存储字符串
                        set key value;
                        get key;
                        userInfo.toString();可以..因为用户信息数据基本上不会超过512m; 选hash；
                        不便于修改的原因，例如user:userId:info phone 13927328473；
                            首先：String userJson = get user:userId:info;
                            然后：UserInfo userInfo = JSON.parseObject(userJson, UserInfo.class);
                        SSO：单点登录——一处登录，处处使用
                            将用户存储在缓存中
                            key = ? value = ?
                            所有的登录都离不开：select * from user_info where userName = ? and pwd = ?;
                            userInfo ! = null
                                setRedis(userInfo);key = ?,value = ? —— key = userId,value = userInfo.toString()
                    list:   队列，先进先出
                    set:    两个接口取交集，共同关注
                    zset:   用来排序

         */
        /*
            登录成功后：login() success:在请求头中存储一个token；如果没有登陆过，请求头中没有token
         */
        // 判断请求头是否存在
        // 从spring容器上下文请求对象中获取
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // httpServletRequest,getHeader("token");
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String token = request.getHeader("token");

        try {
            //判断是否需要登录
            if (tsLogin.required()) {
                // 必须要登录，token为空是抛出异常
                if (StringUtils.isEmpty(token)) {
                    // 没有token要抛出异常
                    throw new ItcamException(ResultCodeEnum.LOGIN_AUTH);
                }
                // 如果不为空，则判断token是否有效
                // 使用redis的string数据类型存储
                // key = ?,value = ?
                // key = userId ❌,value = userInfo.toString()
                // key = token ,value = userInfo.toString()
                // 利用token获取缓存数据，判断缓存数据是否存在！
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
                // 判断对象是否为空
                if (null == userInfo) {
                    // 抛出异常信息
                    throw new ItcamException(ResultCodeEnum.LOGIN_AUTH);
                }
            }
            // 在有些场景下，不需要用户登录也能访问，例如：获取商品详情，获取商品列表，获取商品分类等
        /*
            获取用户id 判断用户Id是否为空
            Long userId = AuthContextHolder.getUserId() == null ? 1L : AuthContextHolder.getUserId();
         */
            // 不需要强制登录，但是，有可能需要用信息
            if (!StringUtils.isEmpty(token)) {
                // 如果token不为空，从缓存中获取信息
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
                // 强制转换为UserInfo对象
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                // 判断当前userInfo对象是否为空
                if (userInfo != null) {
                    // 缓存中有数据，为了后续其他微服务方便获取userId，在此将用户Id放入ThreadLocal
                    AuthContextHolder.setUserId(userInfo.getId());
                    // AuthContextHolder.setUsername(userInfo.getNickname());
                }
            }
            // 要执行带有注解的方法体
            return joinPoint.proceed();
        } finally {
            // 释放内存空间，防止内存泄露
            AuthContextHolder.removeUserId();
        }
    }

}
